{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import io\n",
    "import platform\n",
    "import sklearn\n",
    "import numpy as np\n",
    "from sklearn.model_selection import train_test_split\n",
    "import sklearn.datasets as datasets\n",
    "import random\n",
    "import pandas as pd\n",
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "import torch.utils.data as data\n",
    "from torch.utils.data.dataset import Dataset\n",
    "import torch.nn.functional as F\n",
    "from torch.autograd import Variable\n",
    "\n",
    "import torchvision\n",
    "import torchvision.datasets as datasets\n",
    "import torchvision.transforms as transforms\n",
    "\n",
    "from PIL import Image\n",
    "from PIL import ImageOps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "FILE_PATH='./LAG/'\n",
    "index=[]\n",
    "def load_data():\n",
    "    data=[]\n",
    "    for directory in os.listdir(FILE_PATH):\n",
    "        if not(directory.startswith('.')):\n",
    "            young=[]\n",
    "            old=[]\n",
    "            temp=[]\n",
    "            path=FILE_PATH+directory\n",
    "            for g_img in os.listdir(path):\n",
    "                path1=path+'/'+g_img\n",
    "                copy_g=g_img\n",
    "                if not(g_img.startswith('.')) and os.path.splitext(copy_g)[1]=='.png':\n",
    "                    old.append(Image.open(path1).convert('RGB'))\n",
    "                elif not(g_img.startswith('.')):\n",
    "                    for y_img in os.listdir(path1):\n",
    "                        if not (y_img.startswith('.')):\n",
    "                            young.append(Image.open(path1+'/'+y_img).convert('RGB'))\n",
    "            temp.append(old)\n",
    "            temp.append(young)\n",
    "            data.append(temp)\n",
    "    return data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "data=load_data()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset=[]\n",
    "for i in range(0,len(data)):\n",
    "    olds=data[i][0]\n",
    "    youngs=data[i][1]\n",
    "    for old in olds:\n",
    "        for young in youngs:\n",
    "            temp=[transforms.ToTensor()(old),transforms.ToTensor()(young)]\n",
    "            dataset.append(temp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[tensor([[[ 0.9176,  0.9137,  0.9098,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          [ 0.9176,  0.9137,  0.9098,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          [ 0.9176,  0.9176,  0.9137,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          ...,\n",
       "          [ 0.1333,  0.1412,  0.1255,  ...,  0.2039,  0.1922,  0.2196],\n",
       "          [ 0.1412,  0.1333,  0.1059,  ...,  0.2118,  0.2118,  0.2039],\n",
       "          [ 0.2000,  0.1647,  0.1020,  ...,  0.2000,  0.2353,  0.2078]],\n",
       " \n",
       "         [[ 0.9176,  0.9137,  0.9098,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          [ 0.9176,  0.9137,  0.9098,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          [ 0.9176,  0.9176,  0.9137,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          ...,\n",
       "          [ 0.1333,  0.1412,  0.1255,  ...,  0.1216,  0.1059,  0.1255],\n",
       "          [ 0.1412,  0.1333,  0.1059,  ...,  0.1216,  0.1216,  0.1137],\n",
       "          [ 0.2000,  0.1647,  0.1020,  ...,  0.1098,  0.1451,  0.1176]],\n",
       " \n",
       "         [[ 0.9176,  0.9137,  0.9098,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          [ 0.9176,  0.9137,  0.9098,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          [ 0.9176,  0.9176,  0.9137,  ...,  0.0000,  0.0000,  0.0000],\n",
       "          ...,\n",
       "          [ 0.1333,  0.1412,  0.1255,  ...,  0.0667,  0.0510,  0.0745],\n",
       "          [ 0.1412,  0.1333,  0.1059,  ...,  0.0667,  0.0667,  0.0588],\n",
       "          [ 0.2000,  0.1647,  0.1020,  ...,  0.0510,  0.0824,  0.0627]]]),\n",
       " tensor([[[ 0.9294,  0.9176,  0.9176,  ...,  0.8941,  0.8941,  0.9020],\n",
       "          [ 0.9529,  0.9451,  0.9294,  ...,  0.8980,  0.9020,  0.9098],\n",
       "          [ 0.9333,  0.9333,  0.9294,  ...,  0.9059,  0.9098,  0.9137],\n",
       "          ...,\n",
       "          [ 0.1647,  0.1608,  0.1373,  ...,  0.0353,  0.5098,  0.8745],\n",
       "          [ 0.0941,  0.0863,  0.0745,  ...,  0.0275,  0.5020,  0.8627],\n",
       "          [ 0.1373,  0.1451,  0.1843,  ...,  0.0275,  0.5020,  0.8510]],\n",
       " \n",
       "         [[ 0.9294,  0.9176,  0.9176,  ...,  0.8941,  0.8941,  0.9020],\n",
       "          [ 0.9529,  0.9451,  0.9294,  ...,  0.8980,  0.9020,  0.9098],\n",
       "          [ 0.9333,  0.9333,  0.9294,  ...,  0.9059,  0.9098,  0.9137],\n",
       "          ...,\n",
       "          [ 0.1647,  0.1608,  0.1373,  ...,  0.0353,  0.4941,  0.8588],\n",
       "          [ 0.0941,  0.0863,  0.0745,  ...,  0.0275,  0.4902,  0.8510],\n",
       "          [ 0.1373,  0.1451,  0.1843,  ...,  0.0275,  0.4980,  0.8510]],\n",
       " \n",
       "         [[ 0.9294,  0.9176,  0.9176,  ...,  0.8941,  0.8941,  0.9020],\n",
       "          [ 0.9529,  0.9451,  0.9294,  ...,  0.8980,  0.9020,  0.9098],\n",
       "          [ 0.9333,  0.9333,  0.9294,  ...,  0.9059,  0.9098,  0.9137],\n",
       "          ...,\n",
       "          [ 0.1647,  0.1608,  0.1373,  ...,  0.0353,  0.4824,  0.8510],\n",
       "          [ 0.0941,  0.0863,  0.0745,  ...,  0.0275,  0.4824,  0.8471],\n",
       "          [ 0.1373,  0.1451,  0.1843,  ...,  0.0275,  0.4941,  0.8510]]])]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dataset[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "train,test=train_test_split(dataset,test_size=0.2,shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "train,valid=train_test_split(train,test_size=0.25,shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(CNN,self).__init__()\n",
    "        self.cl1=nn.Conv2d(3,12,kernel_size=5,padding=2) # tunable here\n",
    "        self.mp1=nn.MaxPool2d(2)\n",
    "        self.cl2=nn.Conv2d(12,3,kernel_size=5,padding=2)\n",
    "        self.mp2=nn.MaxPool2d(2)\n",
    "        self.fcl=nn.Linear(7500,200) # 12 x 27 x 27\n",
    "        \n",
    "    def forward(self,x):\n",
    "        # x dim: b x 800 x 600 x 3 -> b x 1 x 100 x 100\n",
    "        out=F.relu(self.cl1(x))\n",
    "        out=self.mp1(out)\n",
    "        out=F.relu(self.cl2(out))\n",
    "        out=self.mp2(out)\n",
    "        \n",
    "        # tensor size of b x inchannels x 1 x 1\n",
    "        out=out.view(out.size(0),-1)\n",
    "        out=F.sigmoid(self.fcl(out))\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ContrastiveLoss(torch.nn.Module):\n",
    "    \"\"\"\n",
    "    Contrastive loss function.\n",
    "    Based on: http://yann.lecun.com/exdb/publis/pdf/hadsell-chopra-lecun-06.pdf\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, margin=2.0):\n",
    "        super(ContrastiveLoss, self).__init__()\n",
    "        self.margin = margin\n",
    "\n",
    "    def forward(self, output1, output2):\n",
    "        euclidean_distance = F.pairwise_distance(output1, output2)\n",
    "        loss_contrastive = torch.mean(torch.pow(euclidean_distance, 2) +\n",
    "                                      torch.pow(torch.clamp(self.margin - euclidean_distance, min=0.0), 2))\n",
    "\n",
    "#         loss_contrastive=torch.mean(torch.pow(euclidean_distance,2))\n",
    "        return loss_contrastive"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "model=CNN()\n",
    "model.eval()\n",
    "optimizer=optim.Adam(model.parameters(),lr=0.0001,weight_decay=0.0001)\n",
    "criterion=ContrastiveLoss()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def trainCNN(epoch,model,train_loader,optimizer):\n",
    "    model.train()\n",
    "    \n",
    "    total_loss=0\n",
    "    correct=0\n",
    "    \n",
    "    for i, (image,label) in enumerate(train_loader):\n",
    "        optimizer.zero_grad() # initialize   \n",
    "        prediction1=model(image)\n",
    "        prediction2=model(label)\n",
    "        loss=criterion(prediction1,prediction2)\n",
    "#         print(loss)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        total_loss+=loss # update loss\n",
    "#         pred_classes=prediction1.data.max(1,keepdim=True)[1]\n",
    "#         correct+=pred_classes.eq(prediction2.long().data.view_as(pred_classes)).sum().double()\n",
    "        \n",
    "    mean_loss=total_loss/len(train_loader.dataset)\n",
    "#     acc=correct/len(train_loader.dataset)\n",
    "    \n",
    "    print('Train Epoch: {}   Avg_Loss: {:.5f}'.format(\n",
    "        epoch, mean_loss))\n",
    "    return mean_loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evalCNN(model,test_loader):\n",
    "    model.eval()\n",
    "    \n",
    "    total_loss=0\n",
    "    correct=0\n",
    "    \n",
    "    for i, (image,label) in enumerate(test_loader):\n",
    "        optimizer.zero_grad() # initialize \n",
    "        prediction1=model(image)\n",
    "        prediction2=model(label)\n",
    "        loss=criterion(prediction1,prediction2)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        total_loss+=loss # update loss\n",
    "#         pred_classes=prediction1.data.max(1,keepdim=True)[1]\n",
    "#         correct+=pred_classes.eq(prediction2.long().data.view_as(pred_classes)).sum().double()\n",
    "        \n",
    "    mean_loss=total_loss/len(test_loader.dataset)\n",
    "#     acc=correct/len(test_loader.dataset)\n",
    "    \n",
    "    print('Eval:   Avg_Loss: {:.5f}'.format(\n",
    "         mean_loss))\n",
    "\n",
    "    return mean_loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def save_model(epoch, model, path='./'):\n",
    "    filename=path+'model.pt'\n",
    "    torch.save(model.state_dict(), filename)\n",
    "    return model\n",
    "\n",
    "def load_model(epoch, model, path='./'):\n",
    "#     torch.save(model.state_dict(), filename)\n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "class LAGDataset(Dataset):\n",
    "    \"\"\"LAG dataset.\"\"\"\n",
    "\n",
    "    def __init__(self, data):\n",
    "        self.data = data\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.data)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "#         img_name = os.path.join(self.root_dir,\n",
    "#                                 self.landmarks_frame.iloc[idx, 0])\n",
    "#         image = io.imread(img_name)\n",
    "#         landmarks = self.landmarks_frame.iloc[idx, 1:].as_matrix()\n",
    "#         landmarks = landmarks.astype('float').reshape(-1, 2)\n",
    "#         sample = {'image': image, 'landmarks': landmarks}\n",
    "\n",
    "#         if self.transform:\n",
    "#             sample = self.transform(sample)\n",
    "        \n",
    "        img=self.data[idx]\n",
    "\n",
    "        return (img[0],img[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_set=LAGDataset(train)\n",
    "valid_set=LAGDataset(valid)\n",
    "test_set=LAGDataset(test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "BATCH_SIZE=100\n",
    "train_loader=torch.utils.data.DataLoader(train_set,batch_size=BATCH_SIZE,shuffle=True)\n",
    "valid_loader=torch.utils.data.DataLoader(valid_set,batch_size=BATCH_SIZE,shuffle=True)\n",
    "test_loader=torch.utils.data.DataLoader(test_set,batch_size=BATCH_SIZE,shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Train Epoch: 1   Avg_Loss: 0.02769\n",
      "Eval:   Avg_Loss: 0.02437\n",
      "Train Epoch: 2   Avg_Loss: 0.02224\n",
      "Eval:   Avg_Loss: 0.02330\n",
      "Train Epoch: 3   Avg_Loss: 0.02159\n",
      "Eval:   Avg_Loss: 0.02283\n",
      "Train Epoch: 4   Avg_Loss: 0.02126\n",
      "Eval:   Avg_Loss: 0.02251\n",
      "Train Epoch: 5   Avg_Loss: 0.02106\n",
      "Eval:   Avg_Loss: 0.02233\n",
      "Train Epoch: 6   Avg_Loss: 0.02090\n",
      "Eval:   Avg_Loss: 0.02217\n",
      "Train Epoch: 7   Avg_Loss: 0.02080\n",
      "Eval:   Avg_Loss: 0.02213\n",
      "Train Epoch: 8   Avg_Loss: 0.02073\n",
      "Eval:   Avg_Loss: 0.02204\n",
      "Train Epoch: 9   Avg_Loss: 0.02068\n",
      "Eval:   Avg_Loss: 0.02198\n",
      "Train Epoch: 10   Avg_Loss: 0.02064\n",
      "Eval:   Avg_Loss: 0.02198\n",
      "\n",
      "\n",
      "\n",
      "Optimization ended.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "num_epoch=10\n",
    "checkpoint_freq=5\n",
    "path=\"./\"\n",
    "\n",
    "train_losses = []\n",
    "test_losses = []\n",
    "\n",
    "train_accuracies = []\n",
    "test_accuracies = []\n",
    "\n",
    "# traininng \n",
    "for epoch in range(1, num_epoch + 1):\n",
    "    \n",
    "    # train() function (see above)\n",
    "    train_loss = trainCNN(epoch, model, train_loader, optimizer)\n",
    "    \n",
    "    # eval() functionn (see above)\n",
    "    test_loss = evalCNN(model, test_loader)    \n",
    "    \n",
    "    # append lists for plotting and printing \n",
    "    train_losses.append(train_loss)    \n",
    "    test_losses.append(test_loss)\n",
    "    \n",
    "#     train_accuracies.append(train_acc)    \n",
    "#     test_accuracies.append(test_acc)\n",
    "    \n",
    "    # Checkpoint\n",
    "    if epoch % checkpoint_freq == 0:\n",
    "        save_model(epoch, model, path)\n",
    "\n",
    "# Last checkpoint\n",
    "save_model(num_epoch, model, path)\n",
    "    \n",
    "print(\"\\n\\n\\nOptimization ended.\\n\") "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "img1=transforms.ToTensor()(Image.open('./LAG/stas_mikhailov/Aprile_01891_1.png').convert('RGB'))\n",
    "img2=transforms.ToTensor()(Image.open('./LAG/stas_mikhailov/y/Aprile_01891_2.png').convert('RGB'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "p1=model(img1.unsqueeze(0))\n",
    "p2=model(img2.unsqueeze(0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "loss=criterion(p1,p2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor(2.0030)"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "total_loss=0\n",
    "for i, (old,young) in enumerate(valid_loader):\n",
    "    prediction1=model(old)\n",
    "    prediction2=model(young)\n",
    "    loss=criterion(prediction1,prediction2)\n",
    "    total_loss+=loss\n",
    "mean_loss=total_loss/len(valid_loader.dataset)\n",
    "print(mean_loss)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import os\n",
    "import io\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "train_losses=[0.02769,0.02224,0.02159,0.02126,0.02106,0.02090,0.02080,0.02073,0.02068,0.02064]\n",
    "valid_losses=[0.02437,0.02330,0.02283,0.02251,0.02233,0.02217,0.02213,0.02204,0.02198,0.02198]\n",
    "\n",
    "plt.plot(train_losses, color=\"darkcyan\", label=\"train\")\n",
    "plt.plot(valid_losses, color=\"tomato\",label=\"validation\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
